package com.pine.app.module.security.oauth.support;


import com.pine.app.module.security.core.common.RequestUtils;
import com.pine.app.module.security.core.common.AuthConstant;
import com.pine.app.module.security.core.common.enums.ErrorType;
import com.pine.app.module.security.oauth.exception.GrantTokenException;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.net.URLDecoder;
import java.util.*;

/**
 * @author xiaoyuan
 * @create 2019/9/30 9:23
 **/
public abstract class AbstractHttpTokenRequest implements HttpTokenRequest {

    protected Map<String, String> parameters;
    protected Map<String, String> headers;
    protected Set<String> scope;
    protected String grantType;


    protected ClientCredentials clientCredentials;
    public AbstractHttpTokenRequest(HttpServletRequest request) {
        this.parameters = RequestUtils.getParameters(request);
        this.headers = RequestUtils.getHeaders(request);
        String clientId = parameters.get(AuthConstant.client_id);
        String clientSecret = parameters.get(AuthConstant.client_secret);
        String authorization = StringUtils.isBlank(headers.get(AuthConstant.authorization))
                ? headers.get(AuthConstant.authorization.toLowerCase()) : headers.get(AuthConstant.authorization);
        this.grantType = parameters.get(AuthConstant.grant_type);
        clientCredentials = getClientCredentials(clientId, clientSecret, authorization);

        this.scope = getParameter(AuthConstant.scope)
                .filter(scopeStr -> !StringUtils.isBlank(scopeStr))
                .map(scopeStr -> new HashSet<>(Arrays.asList(scopeStr.split("[, \n]"))))
                .orElseGet(HashSet::new);
    }

    public AbstractHttpTokenRequest(){

    }

    public AbstractHttpTokenRequest(Map<String, String> parameters,Map<String, String> headers, Set<String> scope, String grantType,ClientCredentials clientCredentials){
        this.parameters = parameters;
        this.headers = headers;
        this.scope = scope;
        this.grantType = grantType;
        this.clientCredentials = clientCredentials;
    }

    @Override
    public Map<String, String> getParameters() {
        return parameters;
    }

    @Override
    public String getGrantType() {
        return grantType;
    }

    @Override
    public ClientCredentials getClientCredentials() {
        return clientCredentials;
    }

    @Override
    public Set<String> getScope() {
        return scope;
    }

    protected ClientCredentials getClientCredentials(String principal, String credentials, String authorization) {
        if (StringUtils.isNotBlank(authorization)) {
            String[] decodeCredentials = decodeClientAuthenticationHeader(authorization);
            //fix #63
            if (decodeCredentials == null) {
                return null;
            }
            if (decodeCredentials.length > 1) {
                principal = decodeCredentials[0];
                credentials = decodeCredentials[1];
            } else {
                credentials = decodeCredentials[0];
            }
        }
        return new ClientCredentials(principal, credentials);
    }


    protected String[] decodeClientAuthenticationHeader(String authenticationHeader) {
        if (StringUtils.isBlank(authenticationHeader)) {
            return null;
        } else {
            String[] tokens = authenticationHeader.split(" ");
            if (tokens.length != 2) {
                return null;
            } else {
                String authType = tokens[0];
                if (!"basic".equalsIgnoreCase(authType)) {
                    return ErrorType.OTHER.throwThis(GrantTokenException::new, "Authentication[" + authType + "]不支持");
                } else {
                    String encodedCreds = tokens[1];
                    return decodeBase64EncodedCredentials(encodedCreds);
                }
            }
        }
    }

    protected String[] decodeBase64EncodedCredentials(String encodedCredentials) {
        String decodedCredentials = new String(Base64.getDecoder().decode(encodedCredentials));
        String[] credentials = decodedCredentials.split(":", 2);
        if (credentials.length != 2) {
            decodedCredentials = URLDecoder.decode(decodedCredentials);
            credentials = decodedCredentials.split(":", 2);
        }
        return credentials.length != 2 ? null : (!StringUtils.isBlank(credentials[0]) && !StringUtils.isBlank(credentials[1]) ? credentials : null);
    }
}
